# -*- coding: utf-8 -*-
import json
import logging
import time

from common.cache import redis_cache
from common.channel import admin_db as channel_admin_db
from common.channel import db as channel_db
from common.channel.channel_map import CHANNEL_HANDLER
from common.mch import db as mch_db
from common.order import db as order_db
from common.order.mg_stat import mg_channel_create_statistic, check_user_pay_count_is_over_limist
from common.strategy import db as strategy_db
from common.utils import exceptions as err
from common.utils import track_logging
from common.utils import tz
from common.utils.metrics import Metric
from common.utils.weight_sample import weight_sample

_LOGGER = track_logging.getLogger(__name__)
_TEST_LOG = logging.getLogger('test')
_TRACKER = logging.getLogger('tracker')


def filter_chn_by_money(candidates, money):
    index = 0
    result = []
    the_min = 1000
    the_index = 0
    for chn in candidates:
        chn_info = json.loads(chn.info)
        min_amount = chn_info.get('min_amount', 1)
        if the_min > min_amount:
            the_min = min_amount
            the_index = index
        index = index + 1
        if money < min_amount:
            continue
        result.append(chn)
    if len(result) == 0:
        result.append(candidates[the_index])
    _LOGGER.info('filter_chn_by_money %s', len(result))
    return result


def _choose_chn(available_channels, sdk_version, money, service, mch_id, user_id, chn_type=None, level=-1):
    # choose one
    candidates = []
    for chn in available_channels:
        if check_user_pay_count_is_over_limist(mch_id, user_id, chn.id):
            continue
        if 'quota' in service and float(money) not in [float(y) for y in chn.quotas.split(',')]:
            continue
        if chn.levels and chn.levels != '':
            levels = [int(x) for x in chn.levels.split(',')]
            if level != -1 and level not in levels:
                continue
        chn_info = json.loads(chn.info)
        # sdk_filters = chn_info.get('sdk_version', {})

        # if sdk_version in sdk_filters.get('exclude', []):
        #     continue
        if _check_channel_daily_vaild_time(chn_info):
            continue
        if _check_channel_is_risk(chn.id, chn_info):
            continue
        if _check_channel_amount_in_range(money, chn_info):
            continue
        # if sdk_version in sdk_filters.get('include', []):
        candidates.append(chn)
        if chn_type and int(chn_type) == chn.chn_type:
            return chn

    if not candidates:
        _LOGGER.warn('no candidates for sdk {}'.format(sdk_version))
        raise err.PermissionError(u'mch chn not available')

    # add weight
    c_dict = dict([(i, c.weight) for i, c in enumerate(candidates)])
    chosed_index = weight_sample(c_dict.keys(), c_dict, 1).next()
    return candidates[chosed_index]


def _check_strategy(pay, total_fee, chn_type, chn_info):
    charge_extra = json.loads(chn_info['extra'])
    user_info = charge_extra.get('user_info', {})
    user_chn = user_info.get('chn')
    user_id = user_info.get('user_id')
    strategy = strategy_db.get_enable_strategy(pay.mch_id, user_chn)
    if strategy:
        try:
            content = json.loads(strategy.content)
            strategy_type = content['type']
            if strategy_type == 's1':  # 每个玩家在n分钟内只能创建m个订单
                interval_ts = int(content['interval_ts'])
                last_order_ts = redis_cache.get_last_order_ts(pay.mch_id, user_id)
                nowts = tz.now_ts()
                if nowts - last_order_ts < interval_ts:
                    raise err.ReachLimit('user reached limit, 1/%s' % interval_ts)
        except err.ReachLimit as e:
            raise e
        except:
            _LOGGER.warn('_check_strategy, strategy content %s is not valid json',
                         strategy.content)


def _check_channel_is_risk(channel_id, chn_info):
    # 检查累积充值风控
    try:
        # _LOGGER.info('check _check_channel_is_risk channel_id:%s, chn_info: %s ', channel_id, chn_info)
        if 'incr_risk' in chn_info:
            daily_stats, hourly_stats, minutely_stats = redis_cache.get_chn_amount(channel_id)
            minutes_chn_count = redis_cache.get_minutes_chn_count(channel_id)
            risk_conf = chn_info['incr_risk']
            if 'daily' in risk_conf:
                daily_conf = risk_conf['daily']
                amount, count = daily_conf.get('amount', 0), daily_conf.get('count', 1000000)
                if amount == 0 or int(daily_stats.get('amount', 0)) >= amount:
                    raise err.ReachLimit('chn reached daily amount limit, %s' % channel_id)
                if count and int(daily_stats.get('count', 0)) >= count:
                    raise err.ReachLimit('chn reached daily count limit, %s' % channel_id)
            if 'hourly' in risk_conf:
                hourly_conf = risk_conf['hourly']
                amount, count = hourly_conf.get('amount', 0), hourly_conf.get('count', 1000000)
                if amount == 0 or int(hourly_stats.get('amount', 0)) >= amount:
                    raise err.ReachLimit('chn reached hourly amount limit, %s' % channel_id)
                if count and int(hourly_stats.get('count', 0)) >= count:
                    raise err.ReachLimit('chn reached hourly count limit, %s' % channel_id)
            if 'minutely' in risk_conf:
                minutely_conf = risk_conf['minutely']
                count = minutely_conf.get('count', 1000000)
                if int(minutely_stats.get('count', 0)) >= count:
                    raise err.ReachLimit('chn reached minutely count limit, %s' % channel_id)
                # TODO: 渠道短缺，先暂时处理
                if int(minutes_chn_count.get('count', 0)) >= count:
                    raise err.ReachLimit('chn reached minutes count limit, %s' % channel_id)

    except Exception as e:
        _LOGGER.warn('check incr_risk warn, %s', e)
        return True
    return False


def _check_channel_amount_in_range(amount, chn_info):
    try:
        min_amount = None
        max_amount = None

        if 'min_amount' in chn_info and 'max_amount' in chn_info:
            min_amount = chn_info['min_amount']
            max_amount = chn_info['max_amount']

        if min_amount and max_amount and \
            amount < min_amount or amount > max_amount:
            return True
    except Exception as e:
        _LOGGER.exception('_check_channel_amount_in_range exceptions, %s', e)
        return True
    return False


def _check_channel_daily_vaild_time(chn_info):
    try:
        if 'daily_open_time' in chn_info:
            daily_open_time = chn_info['daily_open_time']
            start = daily_open_time.get('start', 0)
            end = daily_open_time.get('end', 24)
            hour = tz.local_now().hour
            if start > end:  # 跨日
                if hour > end:
                    end += 24
                else:
                    end += 24
                    hour += 24
            if hour < start or hour >= end:
                return True
    except Exception as e:
        _LOGGER.exception('_check_channel_daily_vaild_time exceptions, %s', e)
        return False
    return False


def _record_strategy_ref(mch_id, chn_info, pay):
    charge_extra = json.loads(chn_info['extra'])
    user_info = charge_extra.get('user_info', {})
    user_id = user_info.get('user_id')
    redis_cache.save_last_order_ts(mch_id, user_id)
    # redis_cache.add_chn_amount(pay.channel_id, pay.total_fee)


_IP_BLACK_LIST = ['110.102.55.187', '117.136.71.192', '223.104.94.49', '117.136.73.203',
                  '223.104.247.19', '117.136.89.105', '106.109.83.246', '36.98.153.115',
                  '171.82.159.141', '117.154.165.126', '171.82.153.197', '171.37.33.37',
                  '171.44.152.235', '42.249.61.123', '117.136.85.224']


def _get_device_ip(info):
    try:
        extra = json.loads(info['extra'])
    except:
        extra = {}
    user_info = extra.get('user_info', {})
    return user_info.get('device_ip') or '127.0.0.1'


def _check_device_ip(ip):
    if ip in _IP_BLACK_LIST:
        _LOGGER.info('_check_device_ip : %s in black list', ip)
        raise err.PermissionError(u'ip[%s] is in black list' % ip)


def _get_userid_from_extra(extra):
    j = json.loads(extra)
    t = j.get('user_info', {})
    return t.get('user_id', 0)


def create_charge_by_channel_id(charge_req, channel_id):
    total_fee = float(charge_req['total_fee'])
    chn = channel_admin_db.get_channel(int(channel_id))
    chn_type = chn.chn_type
    chn_id = chn.id
    open_type = chn.open_type

    chn_info = json.loads(chn.info)
    chn_info.update({
        'sdk_version': charge_req['sdk_version'],
        'body': charge_req['body'],
        'extra': charge_req.get('extra', '{}')
    })
    # create pay order
    pay = order_db.create_order(charge_req, chn_id, chn_type)

    mg_channel_create_statistic(pay)

    chn_handler = CHANNEL_HANDLER[chn_type]
    charge_resp = chn_handler.create_charge(pay, total_fee, chn_info)
    if isinstance(charge_resp['charge_info'], basestring):
        charge_resp['charge_info'] = charge_resp['charge_info'].replace('HTTPS://QR.ALIPAY.COM/',
                                                                        'https://qr.alipay.com/')
    charge_resp.update({
        'channel_type': chn_type,
        'open_type': open_type,
        'result': 'ok',
        'order_id': pay.id
    })
    return charge_resp


def create_charge(charge_req, metric, channel_level=-1):
    assert isinstance(metric, Metric)
    if charge_req.get('channel_id'):
        metric.measure('begin channel')
        result = create_charge_by_channel_id(charge_req, charge_req.get('channel_id'))
        metric.measure('begin channel')
        return result

    mch_id = charge_req['mch_id']
    service = charge_req['service']
    user_id = _get_userid_from_extra(charge_req.get('extra'))
    metric.measure('begin service conf')
    service_conf = mch_db.get_mch_chn(mch_id, service)
    metric.measure('end service conf')
    if not service_conf:
        raise err.PermissionError(u'mch id not auth')
    channel_list = service_conf.chns.split(',')
    metric.measure('begin available channel')
    available_channels = channel_db.get_channels_in_ids(channel_list)
    metric.measure('end available channel')
    if not available_channels:
        raise err.PermissionError(u'mch chn is empty')

    total_fee = float(charge_req['total_fee'])
    metric.measure('begin choose channel')
    chn = _choose_chn(available_channels, charge_req['sdk_version'], total_fee, service, mch_id, user_id,
                      charge_req.get('chn_type'), channel_level)
    metric.measure('end choose channel')
    chn_type = chn.chn_type
    chn_id = chn.id
    open_type = chn.open_type

    chn_info = json.loads(chn.info)
    chn_info.update({
        'sdk_version': charge_req['sdk_version'],
        'body': charge_req['body'],
        'notify_prefix': service_conf.notify_prefix,
        'extra': charge_req.get('extra', '{}')
    })

    charge_req['total_fee'] = total_fee

    if charge_req.get('metadata'):
        chn_info['metadata'] = charge_req['metadata']

    # create pay order
    metric.measure('start create order')
    pay = order_db.create_order(charge_req, chn_id, chn_type)
    metric.measure('end create order')

    # 移到前面记录 避免创建订单出现异常 无法记录
    metric.measure('start channel stat')
    mg_channel_create_statistic(pay)
    metric.measure('end channel stat')

    # redis统计用户下单次数
    redis_cache.add_user_pay_count(mch_id, user_id)

    # submit pay order to channel
    chn_handler = CHANNEL_HANDLER[chn_type]
    # 走支付策略
    metric.measure('start strategy')
    _check_strategy(pay, total_fee, chn_type, chn_info)
    metric.measure('end strategy')
    # ip黑名单过滤
    try:
        metric.measure('start create channel order')
        charge_resp = chn_handler.create_charge(pay, total_fee, chn_info)
        metric.measure('end create channel order')
    except Exception as e:
        _TRACKER.info({'order_id': pay.id, 'user_id': user_id, 'mch_id': mch_id, 'type': 'recharge',
                       'price': total_fee, 'service': service,
                       'chn_id': chn_id, 'error': str(e)})
        _LOGGER.exception('create channel order by third fail')
        raise err.PermissionError(u'channel_id[%s] is error' % chn_id)
    if isinstance(charge_resp['charge_info'], basestring):
        charge_resp['charge_info'] = charge_resp['charge_info'].replace('HTTPS://QR.ALIPAY.COM/',
                                                                        'https://qr.alipay.com/')
    charge_resp.update({
        'channel_type': chn_type,
        'open_type': open_type,
    })

    # 记录渠道成功回调次数
    redis_cache.add_minutes_chn_count(chn_id)

    _record_strategy_ref(mch_id, chn_info, pay)
    return charge_resp


class tPay(object):
    pass


def get_channel_pay_result(channel_id):
    chn = channel_admin_db.get_channel(int(channel_id))
    if chn is None:
        return
    chn_type = chn.chn_type
    open_type = chn.open_type
    chn_info = json.loads(chn.info)
    min_amount = chn_info.get('min_amount', 1)
    chn_handler = CHANNEL_HANDLER[chn.chn_type]
    pay = tPay()
    pay.id = '111111' + str(int(time.time()))
    pay.user_id = 137258
    pay.extend = '''{}'''
    charge_resp = chn_handler.create_charge(pay, min_amount, chn_info)
    if isinstance(charge_resp['charge_info'], basestring):
        charge_resp['charge_info'] = charge_resp['charge_info'].replace('HTTPS://QR.ALIPAY.COM/',
                                                                        'https://qr.alipay.com/')
    charge_resp.update({
        'channel_type': chn_type,
        'open_type': open_type,
    })
    return charge_resp


def get_order(mch_id, out_trade_no):
    order = order_db.get_order(mch_id, out_trade_no)
    return {
        'out_trade_no': order.out_trade_no,
        'total_fee': order.total_fee,
        'status': order.status,
        'extra': order.extra,
        'channel_type': order.channel_type,
        'third_id': order.third_id,
        'payed_at': tz.utc_to_local_str(order.payed_at) if order.payed_at else None,
    }
